﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OmronLib.Base;
using DataType = OmronLib.Base.DataType;

namespace OmronLib
{
    public class Fins : FinsCommand
    {
        SerialPort _serialPort;

        public Fins(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBit)
        {
            _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBit);
        }

        public override void Open(int timeout = 3000)
        {
            _serialPort.ReadTimeout = timeout;
            _serialPort.Open();
        }

        public override void Close()
        {
            if (_serialPort != null && _serialPort.IsOpen)
            {
                _serialPort.Close();
                _serialPort = null;
            }

        }

        protected override void CheckResponse(byte[] bytes)
        {
            string resp = Encoding.ASCII.GetString(bytes);
            // 检查 FCS 校验码
            string respBody = resp.Substring(0, resp.Length - 4);
            string fcs = resp.Substring(respBody.Length, 2);
            if (fcs != FCS(respBody))
                throw new Exception("FCS 校验错误");

            string statusCode = resp.Substring(19, 4);
            if (statusCode != "0000")
            {
                if (FINSResponseErrors.Errors.ContainsKey(statusCode))
                    throw new Exception(FINSResponseErrors.Errors[statusCode]);
                else
                    throw new Exception("未知错误!");
            }
        }

        private byte[] SendAndReceive(byte[] reqBytes)
        {
            _serialPort.Write(reqBytes, 0, reqBytes.Length);
            List<byte> result = new List<byte>();
            bool isHead = true;
            while (true)
            {
                byte b = (byte)_serialPort.ReadByte();
                if (isHead && (char)b == '@')
                {
                    result.Add(b);
                    isHead = false;
                    continue;
                }

                // 如果是后续填充字节的话则继续执行
                if (isHead) continue;
                result.Add(b);
                // \n \r => 0x0A 0x0D
                if (b == 0x0D && (char)result[result.Count - 2] == '*') // *\r
                    break;
            }
            return result.ToArray();
        }

        public List<byte> Read(int unit, FinsParameter parameter)
        {
            return this.Read(unit, parameter.Area, parameter.WordAddr, parameter.BitAddr, parameter.Count, parameter.DataType);
        }

        public List<byte> Read(int unit, Area area, ushort wordAddr, byte bitAddr, ushort count, DataType dataType)
        {
            if (_serialPort == null || !_serialPort.IsOpen) throw new Exception("串口未初始化");

            if (dataType == DataType.WORD)
                area += 0x80;
            byte a = (byte)area;
            string cmd = $"@{unit.ToString("00")}FA0000000000101{a.ToString("X2")}" +
                $"{wordAddr.ToString("X4")}{bitAddr.ToString("X2")}{count.ToString("X4")}";
            string fcs = FCS(cmd);
            cmd += fcs;
            cmd += "*\r";

            byte[] reqBytes = Encoding.ASCII.GetBytes(cmd);
            byte[] respBytes = this.SendAndReceive(reqBytes);
            this.CheckResponse(respBytes);

            string resp_str = Encoding.ASCII.GetString(respBytes);
            int len = 0;
            if (dataType == DataType.BIT)
                len = count * 2;
            else if (dataType == DataType.WORD)
                len = count * 4;
            string respBody = resp_str.Substring(23, len);
            return Convert.FromHexString(respBody).ToList();
        }

        public void MultipleRead(int unit, params FinsParameter[] parameters)
        {
            if (parameters.Length == 1)
            {
                parameters[0].Data = this.Read(unit, parameters[0]).ToArray();
                return;
            }

            if (_serialPort == null || !_serialPort.IsOpen)
                throw new Exception("串口未初始化");
            string cmd = $"@{unit.ToString("00")}FA0000000000104";
            foreach (var parameter in parameters)
            {
                Area area = parameter.Area;
                if (parameter.DataType == DataType.WORD)
                {
                    area += 0x80;
                }
                byte a = (byte)area;
                cmd += $"{a.ToString("X2")}{parameter.WordAddr.ToString("X4")}{parameter.BitAddr.ToString("X2")}";
            }

            string fcs = FCS(cmd);
            cmd += fcs;
            cmd += "*\r";

            byte[] reqBytes = Encoding.ASCII.GetBytes(cmd);
            byte[] respBytes = this.SendAndReceive(reqBytes);
            this.CheckResponse(respBytes);

            // 数据的解析
            // 0000(索引位置23) 82 xx xx  B0  xx xx 
            string resp_str = Encoding.ASCII.GetString(respBytes);

            int index = 25;
            foreach (FinsParameter param in parameters)
            {
                int len = 2;
                if (param.DataType == DataType.WORD)
                    len = 4;
                string val = resp_str.Substring(index, len);
                param.Data = Convert.FromHexString(val);

                index += len + 2;
            }
        }

        public void Write(int unit, FinsParameter parameter)
        {
            this.Write(unit, parameter.Area, parameter.WordAddr, parameter.BitAddr, parameter.DataType, parameter.Data);
        }

        public void Write(int unit, Area area, ushort wordAddr, byte bitAddr, DataType dataType, byte[] datas)
        {
            if (_serialPort == null || !_serialPort.IsOpen) throw new Exception("串口未初始化");
            int count = datas.Length;
            if (dataType == DataType.WORD)
            {
                area += 0x80;
                if (count % 2 > 0)
                    throw new Exception("需要写入的数据字节长度不正确");
                count = (count / 2);
            }
            byte a = (byte)area;
            string cmd = $"@{unit.ToString("00")}FA0000000000102{a.ToString("X2")}" +
               $"{wordAddr.ToString("X4")}{bitAddr.ToString("X2")}{count.ToString("X4")}";

            cmd += string.Join("", datas.Select(d => d.ToString("X2")));

            string fcs = FCS(cmd);
            cmd += fcs;
            cmd += "*\r";

            byte[] reqBytes = Encoding.ASCII.GetBytes(cmd);
            byte[] respBytes = this.SendAndReceive(reqBytes);
            this.CheckResponse(respBytes);
        }

        public void PlcRun(int unit, RunMode mode = RunMode.RUN)
        {
            if (_serialPort == null || !_serialPort.IsOpen) throw new Exception("串口未初始化");

            string cmd = $"@{unit.ToString("00")}FA0000000000401FFFF{mode.ToString("X2")}";

            string fcs = FCS(cmd);
            cmd += fcs;
            cmd += "*\r";

            byte[] reqBytes = Encoding.ASCII.GetBytes(cmd);
            byte[] respBytes = this.SendAndReceive(reqBytes);
            this.CheckResponse(respBytes);
        }

        public void PlcStop(int unit)
        {
            if (_serialPort == null || !_serialPort.IsOpen) throw new Exception("串口未初始化");

            string cmd = $"@{unit.ToString("00")}FA0000000000402FFFF";
            string fcs = FCS(cmd);
            cmd += fcs;
            cmd += "*\r";

            byte[] reqBytes = Encoding.ASCII.GetBytes(cmd);
            byte[] respBytes = this.SendAndReceive(reqBytes);
            this.CheckResponse(respBytes);
        }

        public DateTime GetPlcClock(int unit)
        {
            if (_serialPort == null || !_serialPort.IsOpen) throw new Exception("串口未初始化");

            string cmd = $"@{unit.ToString("00")}FA0000000000701";

            // 计算 FCS 校验
            var fcs = FCS(cmd);
            cmd += fcs;
            cmd += "*\r";

            byte[] reqBytes = Encoding.ASCII.GetBytes(cmd);
            byte[] respBytes = this.SendAndReceive(reqBytes);
            this.CheckResponse(respBytes);

            // 解析时间数据
            string resp_str = Encoding.Default.GetString(respBytes);
            // resp_str: @00FA00 40 00 00 00 0701 0000 25032616430303 45(FCS)
            string date_str = resp_str.Substring(23, 14);
            
            byte[] date_bytes = Convert.FromHexString(date_str); // 25 => 37  16进制转10进制


            if (!int.TryParse("20" + date_bytes[0].ToString("X2"), out int year))   // 37 => 25   10进制转16进制
            {
                throw new Exception("时间转换失败");
            }
            if (!int.TryParse(date_bytes[1].ToString("X2"), out int month))
            {
                throw new Exception("时间转换失败");
            }
            if (!int.TryParse(date_bytes[2].ToString("X2"), out int day))
            {
                throw new Exception("时间转换失败");
            }
            if (!int.TryParse(date_bytes[3].ToString("X2"), out int hour))
            {
                throw new Exception("时间转换失败");
            }
            if (!int.TryParse(date_bytes[4].ToString("X2"), out int minute))
            {
                throw new Exception("时间转换失败");
            }
            if (!int.TryParse(date_bytes[5].ToString("X2"), out int second))
            {
                throw new Exception("时间转换失败");
            }

            return new DateTime(year, month, day, hour, minute, second);
        }

        public void SetPlcClock(int unit,DateTime dateTime)
        {
            if (_serialPort == null || !_serialPort.IsOpen) throw new Exception("串口未初始化");

            string cmd = $"@{unit.ToString("00")}FA0000000000702";

            cmd += dateTime.ToString("yyMMddHHmm");
            // 计算 FCS 校验
            var fcs = FCS(cmd);
            cmd += fcs;
            cmd += "*\r";

            byte[] reqBytes = Encoding.ASCII.GetBytes(cmd);
            byte[] respBytes = this.SendAndReceive(reqBytes);
            this.CheckResponse(respBytes);
        }
    }
}
